﻿using System;
using System.Collections.Generic;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class represents a mathematical geometric progression.
    /// </summary>
    [Serializable]
    public class GeometricSequence
    {
        /// <summary>
        /// Represents the biggest index of the geometric progression.
        /// </summary>
        private int lastIndex;

        /// <summary>
        /// Represents the start value of the geometric progression.
        /// </summary>
        private double startValue;

        /// <summary>
        /// Represents the constant quotient between two elements.
        /// </summary>
        private double quotient;

        /// <summary>
        /// Includes the numeric data for the geometric progression.
        /// </summary>
        private double[] progressionData;

        /// <summary>
        /// Initializes a new instance of the <see cref="GeometricSequence"/> class.
        /// </summary>
        /// <param name="startValue">The start value (a0) of the geometric progression.</param>
        /// <param name="lastIndex">The biggest index of the geometric progression.</param>
        /// <param name="quotient">The constant quotient between two elements.</param>
        public GeometricSequence(double startValue, int lastIndex, double quotient)
        {
            if (lastIndex == 0)
            {
                throw new ArgumentException("lastIndex == 0", "lastIndex");
            }
            if (lastIndex < 0)
            {
                throw new ArgumentException("lastIndex < 0", "lastIndex");
            }

            this.lastIndex = lastIndex;
            this.startValue = startValue;
            this.quotient = quotient;
            this.progressionData = new double[0];
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GeometricSequence"/> class.
        /// </summary>
        /// <param name="quotient">The constant quotient between two elements.</param>
        /// <param name="progressionData">The progression data in a double array.</param>
        public GeometricSequence(double quotient, double[] progressionData)
        {
            if (progressionData == (double[]) ((object) null))
            {
                throw new ArgumentNullException("progressionData");
            }
            if (progressionData.Length == 0)
            {
                throw new ArgumentException("progressionData.Length == 0", "progressionData");
            }

            this.lastIndex = progressionData.Length - 1;
            this.startValue = progressionData[0];
            this.quotient = quotient;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GeometricSequence"/> class.
        /// </summary>
        /// <param name="progressionData">The progression data in a double array. The quotient value
        /// will be automatically calculated out of the first two elements.</param>
        public GeometricSequence(double[] progressionData)
        {
            if (!GeometricSequence.IsGeometricSequence(progressionData))
            {
                throw new SequenceException("The numerical data does not represents an geometric progression");
            }

            this.lastIndex = progressionData.Length;
            this.startValue = progressionData[0];
            this.quotient = progressionData[1] / progressionData[0];
        }

        /// <summary>
        /// Gets or sets the biggest index of the geometric progression.
        /// </summary>
        /// <value>The last index.</value>
        public int LastIndex
        {
            get { return lastIndex; }
            set { lastIndex = value; }
        }

        /// <summary>
        /// Gets or sets the start value of the geometric progression.
        /// </summary>
        /// <value>The start value.</value>
        public double StartValue
        {
            get { return startValue; }
            set { startValue = value; }
        }

        /// <summary>
        /// Gets or sets the quotient of the geometric progression.
        /// </summary>
        /// <value>The quotient of the geometric progression.</value>
        public double Quotient
        {
            get { return quotient; }
            set { quotient = value; }
        }

        /// <summary>
        /// Gets the numeric data of the progression geometric sequence.
        /// </summary>
        /// <value>The numeric data of the progression geometric sequence.</value>
        public double[] ProgressionData
        {
            get { return progressionData; }
        }

        /// <summary>
        /// Computes the geometric progression out of the given values. This method has to be called
        /// separate, because sometimes the calculation may take a long time.
        /// </summary>
        public void ComputeProgression()
        {
            this.progressionData = new double[this.lastIndex];
            this.progressionData[0] = this.startValue;

            for (int i = 1; i < this.lastIndex; i++)
            {
                progressionData[i] = this.progressionData[i - 1] * this.quotient;
            }
        }

        /// <summary>
        /// Calculates the sum of all elements in the geometric progression.
        /// </summary>
        /// <returns>The sum of the whole progression (s).</returns>
        public double CalculateSum()
        {
            return this.startValue * ((Math.Pow(this.quotient, this.lastIndex) - 1) / (this.quotient - 1));
        }

        /// <summary>
        /// Calculates the geometric average of the sequence.
        /// </summary>
        /// <returns>The geometric average of the sequence.</returns>
        public double CalculateGeometricAverage()
        {
            this.ComputeProgression();

            return ExtendedMath.Root(Arrays.Product(this.progressionData), this.lastIndex);
        }

        /// <summary>
        /// Determines whether [is geometric progression] [the specified progression data].
        /// </summary>
        /// <param name="progressionData">The progression data.</param>
        /// <returns>
        /// 	<c>true</c> if [is geometric progression] [the specified progression data]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsGeometricSequence(double[] progressionData)
        {
            if (progressionData == (double[]) ((object) null))
            {
                throw new ArgumentNullException("progressionData");
            }

            if (progressionData.Length <= 2)
            {
                return false;
            }

            double check = progressionData[1] / progressionData[0];

            for (int i = 1; i < progressionData.Length; i++)
            {
                if (progressionData[i - 1] * check != progressionData[i])
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            String result = "";

            for (int i = 0; i < this.progressionData.Length; i++)
            {
                result += this.progressionData[i] + "; ";
            }

            return result;
        }
    }
}